--[[ -------------------------------------------------------
Implements basic bitwise math.

Does not handle negative numbers, currently.

$URL: http://mushclient-lua-objects.googlecode.com/svn/branches/working/Daesilme/MUSHclient/Util/BitWise.lua $

Copyright (C) 2009 Daesilme (Daniel W. Drake)

This file is part of mushclient-lua-objects.

mushclient-lua-objects is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software
Foundation, either version 2 of the License, or (at your
option) any later version.

mushclient-lua-objects is distributed in the hope that it
will be useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public
License along with mushclient-lua-objects.  If not, see
<http://www.gnu.org/licenses/>.
------------------------------------------------------- ]]--

require("class");

class "BitWise";

local BitWiseClass = BitWise:classtable();

------------------------------------------------------------
-- PUBLIC CLASS PROPERTIES
------------------------------------------------------------

------------------------------------------------------------
-- PUBLIC CLASS METHODS
------------------------------------------------------------
-- Converts the number @str to Base 2.
-- @numbits optionally specifies the number of bits to
--          return.
function BitWiseClass:toBase2(str, numbits)
	local	num			=	self:toBase10(str);
	local	maxPower	=	0;
	local	maxFound	=	false;

	if(num == 0) then
		maxFound	=	true;
		maxPower	=	0;
	end

	-- Find the largest power of 2 that num is divisible by.
	while(not maxFound) do
		if(math.floor((num / (2 ^ maxPower))) == 0) then
			maxFound	=	true;
			maxPower	=	maxPower - 1;
		else
			maxPower	=	maxPower + 1;
		end
	end

	local	bit		=	"";
	local	bits	=	"";

	-- For each power of 2, determine if num is divisible.
	-- If so, add a 1 bit, and subtract from num,
	-- Else add a 0 bit.
	for i = maxPower, 0, -1 do
		bit		=	tostring(math.floor((num / (2 ^ i))));
		bits	=	bits .. bit;
		if(bit == "1") then
			num		=	num - (2 ^ i);
		end
	end

	numbits	=	numbits or string.len(bits);
	bits	=	self:leftPad(bits, numbits, "0");

	return bits;

end   -- BitWiseClass:toBase2(str, numbits)

-- Converts number @str to Base 10.
-- Uses "0b" as a format specifier for binary numbers.
-- Only handles Base 2, 10, and 16.
function BitWiseClass:toBase10(str)

	-- Make sure we have something to convert.
	if(str == nil) then
		error("Cannot convert nil to base 10.");
	end

	-- Can we use Lua's native conversion?
	if(tonumber(str) ~= nil) then
		return tonumber(str);
	end

	-- Split string into base format specifier
	-- and the actual number.
	local base	=	string.lower(string.sub(str, 1, 2));
	local num	= 	string.upper(string.sub(str, 3));

	if(base == "0b") then
		return self:convertBase2To10(num)
	else
		error(
			string.format(
				"[%s] is an unknown base format specifier.",
				base
			)
		);
	end

	return str;

end   -- BitWiseClass:toBase10(str)

-- Convert a Base 2 number to Base 10.
function BitWiseClass:convertBase2To10(num)
	local	rnum	=	string.reverse(num);
	local	len		=	string.len(num);
	local	bit		=	nil;
	local	dec		=	0;

	-- Loop thru the number from right to left.
	for i = 1, len, 1 do

		-- Get the bit
		bit = string.sub(rnum, i, i);

		-- If bit is set, then add relative power of 2.
		if(bit == "1") then
			dec = dec + (2 ^ (i - 1));
		elseif(bit ~= "0") then
			error("Invalid digit in base 2 string");
		end

	end

	return dec;

end   -- BitWiseClass:convertBase2To10(num)

-- Does a bitwise AND of num and mask and
-- returns the value.  Both num and mask
-- must be Base 2, 10, or 16.
function BitWiseClass:BWAnd(num, mask)
	num		=	self:toBase2(num);
	mask	=	self:toBase2(mask);

	local	len		=	math.max(string.len(num), string.len(mask));
	local	nbit	=	nil;
	local	mbit	=	nil;
	local	bits	=	"";

	num		=	self:leftPad(num, len, "0");
	mask	=	self:leftPad(mask, len, "0");

	for i = 1, len, 1 do

		nbit	=	string.sub(num, i, i);
		mbit	=	string.sub(mask, i, i);

		if((nbit == "1") and (mbit == "1")) then
			bits	=	bits .. "1";
		else
			bits	=	bits .. "0";
		end

	end

	return "0b" .. bits;

end   -- BitWiseClass:BWAnd(num, mask)

-- Does a bitwise OR of num and mask and
-- returns the value.  Both num and mask
-- must be Base 2, 10, or 16.
function BitWiseClass:BWOr(num, mask)
	num		=	self:toBase2(num);
	mask	=	self:toBase2(mask);

	local	len		=	math.max(string.len(num), string.len(mask));
	local	nbit	=	nil;
	local	mbit	=	nil;
	local	bits	=	"";

	num		=	self:leftPad(num, len, "0");
	mask	=	self:leftPad(mask, len, "0");

	for i = 1, len, 1 do

		nbit	=	string.sub(num, i, i);
		mbit	=	string.sub(mask, i, i);

		if((nbit == "1") or (mbit == "1")) then
			bits	=	bits .. "1";
		else
			bits	=	bits .. "0";
		end

	end

	return "0b" .. bits;

end   -- BitWiseClass:BWOr(num, mask)

-- Does a bitwise XOR of num and mask and
-- returns the value.  Both num and mask
-- must be Base 2, 10, or 16.
function BitWiseClass:BWXOr(num, mask)
	num		=	self:toBase2(num);
	mask	=	self:toBase2(mask);

	local	len		=	math.max(string.len(num), string.len(mask));
	local	nbit	=	nil;
	local	mbit	=	nil;
	local	bits	=	"";

	num		=	self:leftPad(num, len, "0");
	mask	=	self:leftPad(mask, len, "0");

	for i = 1, len, 1 do

		nbit	=	string.sub(num, i, i);
		mbit	=	string.sub(mask, i, i);

		if(	((nbit == "1") and (mbit == "0")) or
			((nbit == "0") and (mbit == "1")) ) then
			bits	=	bits .. "1";
		else
			bits	=	bits .. "0";
		end

	end

	return "0b" .. bits;

end   -- BitWiseClass:BXOr(num, mask)

------------------------------------------------------------
-- PUBLIC OBJECT PROPERTIES
------------------------------------------------------------

------------------------------------------------------------
-- PUBLIC OBJECT METHODS
------------------------------------------------------------

------------------------------------------------------------
-- PRIVATE CLASS PROPERTIES
------------------------------------------------------------

------------------------------------------------------------
-- PRIVATE CLASS METHODS
------------------------------------------------------------
-- Returns string @str padding with @pad to @len.
function BitWiseClass:leftPad(str, len, pad)

	while(string.len(str) < len) do
		str	=	pad .. str;
	end

	return str;

end ---  leftPad(str, len, pad)

------------------------------------------------------------
-- PRIVATE OBJECT PROPERTIES
------------------------------------------------------------

------------------------------------------------------------
-- PRIVATE OBJECT METHODS
------------------------------------------------------------
